/*
 * Copyright (c) 2016 Universita' di Firenze, Italy
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Author: Tommaso Pecorella <tommaso.pecorella@unifi.it>
 */

#include "rip.h"

#include "ipv4-packet-info-tag.h"
#include "ipv4-route.h"
#include "loopback-net-device.h"
#include "rip-header.h"
#include "udp-header.h"

#include "ns3/abort.h"
#include "ns3/assert.h"
#include "ns3/enum.h"
#include "ns3/log.h"
#include "ns3/names.h"
#include "ns3/node.h"
#include "ns3/random-variable-stream.h"
#include "ns3/uinteger.h"

#include <iomanip>

#define RIP_ALL_NODE "224.0.0.9"
#define RIP_PORT 520

namespace ns3
{

NS_LOG_COMPONENT_DEFINE("Rip");

NS_OBJECT_ENSURE_REGISTERED(Rip);

Rip::Rip()
    : m_ipv4(nullptr),
      m_splitHorizonStrategy(Rip::POISON_REVERSE),
      m_initialized(false)
{
    m_rng = CreateObject<UniformRandomVariable>();
}

Rip::~Rip()
{
}

TypeId
Rip::GetTypeId()
{
    static TypeId tid =
        TypeId("ns3::Rip")
            .SetParent<Ipv4RoutingProtocol>()
            .SetGroupName("Internet")
            .AddConstructor<Rip>()
            .AddAttribute("UnsolicitedRoutingUpdate",
                          "The time between two Unsolicited Routing Updates.",
                          TimeValue(Seconds(30)),
                          MakeTimeAccessor(&Rip::m_unsolicitedUpdate),
                          MakeTimeChecker())
            .AddAttribute("StartupDelay",
                          "Maximum random delay for protocol startup (send route requests).",
                          TimeValue(Seconds(1)),
                          MakeTimeAccessor(&Rip::m_startupDelay),
                          MakeTimeChecker())
            .AddAttribute("TimeoutDelay",
                          "The delay to invalidate a route.",
                          TimeValue(Seconds(180)),
                          MakeTimeAccessor(&Rip::m_timeoutDelay),
                          MakeTimeChecker())
            .AddAttribute("GarbageCollectionDelay",
                          "The delay to delete an expired route.",
                          TimeValue(Seconds(120)),
                          MakeTimeAccessor(&Rip::m_garbageCollectionDelay),
                          MakeTimeChecker())
            .AddAttribute("MinTriggeredCooldown",
                          "Min cooldown delay after a Triggered Update.",
                          TimeValue(Seconds(1)),
                          MakeTimeAccessor(&Rip::m_minTriggeredUpdateDelay),
                          MakeTimeChecker())
            .AddAttribute("MaxTriggeredCooldown",
                          "Max cooldown delay after a Triggered Update.",
                          TimeValue(Seconds(5)),
                          MakeTimeAccessor(&Rip::m_maxTriggeredUpdateDelay),
                          MakeTimeChecker())
            .AddAttribute("SplitHorizon",
                          "Split Horizon strategy.",
                          EnumValue(Rip::POISON_REVERSE),
                          MakeEnumAccessor<SplitHorizonType_e>(&Rip::m_splitHorizonStrategy),
                          MakeEnumChecker(Rip::NO_SPLIT_HORIZON,
                                          "NoSplitHorizon",
                                          Rip::SPLIT_HORIZON,
                                          "SplitHorizon",
                                          Rip::POISON_REVERSE,
                                          "PoisonReverse"))
            .AddAttribute("LinkDownValue",
                          "Value for link down in count to infinity.",
                          UintegerValue(16),
                          MakeUintegerAccessor(&Rip::m_linkDown),
                          MakeUintegerChecker<uint32_t>());
    return tid;
}

int64_t
Rip::AssignStreams(int64_t stream)
{
    NS_LOG_FUNCTION(this << stream);

    m_rng->SetStream(stream);
    return 1;
}

void
Rip::DoInitialize()
{
    NS_LOG_FUNCTION(this);

    bool addedGlobal = false;

    m_initialized = true;

    Time delay =
        m_unsolicitedUpdate + Seconds(m_rng->GetValue(0, 0.5 * m_unsolicitedUpdate.GetSeconds()));
    m_nextUnsolicitedUpdate = Simulator::Schedule(delay, &Rip::SendUnsolicitedRouteUpdate, this);

    for (uint32_t i = 0; i < m_ipv4->GetNInterfaces(); i++)
    {
        Ptr<LoopbackNetDevice> check = DynamicCast<LoopbackNetDevice>(m_ipv4->GetNetDevice(i));
        if (check)
        {
            continue;
        }

        bool activeInterface = false;
        if (m_interfaceExclusions.find(i) == m_interfaceExclusions.end())
        {
            activeInterface = true;
            m_ipv4->SetForwarding(i, true);
        }

        for (uint32_t j = 0; j < m_ipv4->GetNAddresses(i); j++)
        {
            Ipv4InterfaceAddress address = m_ipv4->GetAddress(i, j);
            if (address.GetScope() != Ipv4InterfaceAddress::HOST && activeInterface)
            {
                NS_LOG_LOGIC("RIP: adding socket to " << address.GetLocal());
                TypeId tid = TypeId::LookupByName("ns3::UdpSocketFactory");
                Ptr<Node> theNode = GetObject<Node>();
                Ptr<Socket> socket = Socket::CreateSocket(theNode, tid);
                InetSocketAddress local = InetSocketAddress(address.GetLocal(), RIP_PORT);
                socket->BindToNetDevice(m_ipv4->GetNetDevice(i));
                int ret = socket->Bind(local);
                NS_ASSERT_MSG(ret == 0, "Bind unsuccessful");

                socket->SetRecvCallback(MakeCallback(&Rip::Receive, this));
                socket->SetIpRecvTtl(true);
                socket->SetRecvPktInfo(true);

                m_unicastSocketList[socket] = i;
            }
            else if (m_ipv4->GetAddress(i, j).GetScope() == Ipv4InterfaceAddress::GLOBAL)
            {
                addedGlobal = true;
            }
        }
    }

    if (!m_multicastRecvSocket)
    {
        NS_LOG_LOGIC("RIP: adding receiving socket");
        TypeId tid = TypeId::LookupByName("ns3::UdpSocketFactory");
        Ptr<Node> theNode = GetObject<Node>();
        m_multicastRecvSocket = Socket::CreateSocket(theNode, tid);
        InetSocketAddress local = InetSocketAddress(RIP_ALL_NODE, RIP_PORT);
        m_multicastRecvSocket->Bind(local);
        m_multicastRecvSocket->SetRecvCallback(MakeCallback(&Rip::Receive, this));
        m_multicastRecvSocket->SetIpRecvTtl(true);
        m_multicastRecvSocket->SetRecvPktInfo(true);
    }

    if (addedGlobal)
    {
        Time delay = Seconds(m_rng->GetValue(m_minTriggeredUpdateDelay.GetSeconds(),
                                             m_maxTriggeredUpdateDelay.GetSeconds()));
        m_nextTriggeredUpdate = Simulator::Schedule(delay, &Rip::DoSendRouteUpdate, this, false);
    }

    delay = Seconds(m_rng->GetValue(0.01, m_startupDelay.GetSeconds()));
    m_nextTriggeredUpdate = Simulator::Schedule(delay, &Rip::SendRouteRequest, this);

    Ipv4RoutingProtocol::DoInitialize();
}

Ptr<Ipv4Route>
Rip::RouteOutput(Ptr<Packet> p,
                 const Ipv4Header& header,
                 Ptr<NetDevice> oif,
                 Socket::SocketErrno& sockerr)
{
    NS_LOG_FUNCTION(this << header << oif);

    Ipv4Address destination = header.GetDestination();
    Ptr<Ipv4Route> rtentry = nullptr;

    if (destination.IsMulticast())
    {
        // Note:  Multicast routes for outbound packets are stored in the
        // normal unicast table.  An implication of this is that it is not
        // possible to source multicast datagrams on multiple interfaces.
        // This is a well-known property of sockets implementation on
        // many Unix variants.
        // So, we just log it and fall through to LookupStatic ()
        NS_LOG_LOGIC("RouteOutput (): Multicast destination");
    }

    rtentry = Lookup(destination, true, oif);
    if (rtentry)
    {
        sockerr = Socket::ERROR_NOTERROR;
    }
    else
    {
        sockerr = Socket::ERROR_NOROUTETOHOST;
    }
    return rtentry;
}

bool
Rip::RouteInput(Ptr<const Packet> p,
                const Ipv4Header& header,
                Ptr<const NetDevice> idev,
                const UnicastForwardCallback& ucb,
                const MulticastForwardCallback& mcb,
                const LocalDeliverCallback& lcb,
                const ErrorCallback& ecb)
{
    NS_LOG_FUNCTION(this << p << header << header.GetSource() << header.GetDestination() << idev);

    NS_ASSERT(m_ipv4);
    // Check if input device supports IP
    NS_ASSERT(m_ipv4->GetInterfaceForDevice(idev) >= 0);
    uint32_t iif = m_ipv4->GetInterfaceForDevice(idev);
    Ipv4Address dst = header.GetDestination();

    if (m_ipv4->IsDestinationAddress(header.GetDestination(), iif))
    {
        if (!lcb.IsNull())
        {
            NS_LOG_LOGIC("Local delivery to " << header.GetDestination());
            lcb(p, header, iif);
            return true;
        }
        else
        {
            // The local delivery callback is null.  This may be a multicast
            // or broadcast packet, so return false so that another
            // multicast routing protocol can handle it.  It should be possible
            // to extend this to explicitly check whether it is a unicast
            // packet, and invoke the error callback if so
            return false;
        }
    }

    if (dst.IsMulticast())
    {
        NS_LOG_LOGIC("Multicast route not supported by RIP");
        return false; // Let other routing protocols try to handle this
    }

    if (header.GetDestination().IsBroadcast())
    {
        NS_LOG_LOGIC("Dropping packet not for me and with dst Broadcast");
        if (!ecb.IsNull())
        {
            ecb(p, header, Socket::ERROR_NOROUTETOHOST);
        }
        return false;
    }

    // Check if input device supports IP forwarding
    if (!m_ipv4->IsForwarding(iif))
    {
        NS_LOG_LOGIC("Forwarding disabled for this interface");
        if (!ecb.IsNull())
        {
            ecb(p, header, Socket::ERROR_NOROUTETOHOST);
        }
        return true;
    }
    // Next, try to find a route
    NS_LOG_LOGIC("Unicast destination");
    Ptr<Ipv4Route> rtentry = Lookup(header.GetDestination(), false);

    if (rtentry)
    {
        NS_LOG_LOGIC("Found unicast destination - calling unicast callback");
        ucb(rtentry, p, header); // unicast forwarding callback
        return true;
    }
    else
    {
        NS_LOG_LOGIC("Did not find unicast destination - returning false");
        return false; // Let other routing protocols try to handle this
    }
}

void
Rip::NotifyInterfaceUp(uint32_t i)
{
    NS_LOG_FUNCTION(this << i);

    Ptr<LoopbackNetDevice> check = DynamicCast<LoopbackNetDevice>(m_ipv4->GetNetDevice(i));
    if (check)
    {
        return;
    }

    for (uint32_t j = 0; j < m_ipv4->GetNAddresses(i); j++)
    {
        Ipv4InterfaceAddress address = m_ipv4->GetAddress(i, j);
        Ipv4Mask networkMask = address.GetMask();
        Ipv4Address networkAddress = address.GetLocal().CombineMask(networkMask);

        if (address.GetScope() == Ipv4InterfaceAddress::GLOBAL)
        {
            AddNetworkRouteTo(networkAddress, networkMask, i);
        }
    }

    if (!m_initialized)
    {
        return;
    }

    bool sendSocketFound = false;
    for (auto iter = m_unicastSocketList.begin(); iter != m_unicastSocketList.end(); iter++)
    {
        if (iter->second == i)
        {
            sendSocketFound = true;
            break;
        }
    }

    bool activeInterface = false;
    if (m_interfaceExclusions.find(i) == m_interfaceExclusions.end())
    {
        activeInterface = true;
        m_ipv4->SetForwarding(i, true);
    }

    for (uint32_t j = 0; j < m_ipv4->GetNAddresses(i); j++)
    {
        Ipv4InterfaceAddress address = m_ipv4->GetAddress(i, j);

        if (address.GetScope() != Ipv4InterfaceAddress::HOST && !sendSocketFound && activeInterface)
        {
            NS_LOG_LOGIC("RIP: adding sending socket to " << address.GetLocal());
            TypeId tid = TypeId::LookupByName("ns3::UdpSocketFactory");
            Ptr<Node> theNode = GetObject<Node>();
            Ptr<Socket> socket = Socket::CreateSocket(theNode, tid);
            InetSocketAddress local = InetSocketAddress(address.GetLocal(), RIP_PORT);
            socket->BindToNetDevice(m_ipv4->GetNetDevice(i));
            socket->Bind(local);
            socket->SetRecvCallback(MakeCallback(&Rip::Receive, this));
            socket->SetIpRecvTtl(true);
            socket->SetRecvPktInfo(true);
            m_unicastSocketList[socket] = i;
        }
        if (address.GetScope() == Ipv4InterfaceAddress::GLOBAL)
        {
            SendTriggeredRouteUpdate();
        }
    }

    if (!m_multicastRecvSocket)
    {
        NS_LOG_LOGIC("RIP: adding receiving socket");
        TypeId tid = TypeId::LookupByName("ns3::UdpSocketFactory");
        Ptr<Node> theNode = GetObject<Node>();
        m_multicastRecvSocket = Socket::CreateSocket(theNode, tid);
        InetSocketAddress local = InetSocketAddress(RIP_ALL_NODE, RIP_PORT);
        m_multicastRecvSocket->Bind(local);
        m_multicastRecvSocket->SetRecvCallback(MakeCallback(&Rip::Receive, this));
        m_multicastRecvSocket->SetIpRecvTtl(true);
        m_multicastRecvSocket->SetRecvPktInfo(true);
    }
}

void
Rip::NotifyInterfaceDown(uint32_t interface)
{
    NS_LOG_FUNCTION(this << interface);

    /* remove all routes that are going through this interface */
    for (auto it = m_routes.begin(); it != m_routes.end(); it++)
    {
        if (it->first->GetInterface() == interface)
        {
            InvalidateRoute(it->first);
        }
    }

    for (auto iter = m_unicastSocketList.begin(); iter != m_unicastSocketList.end(); iter++)
    {
        NS_LOG_INFO("Checking socket for interface " << interface);
        if (iter->second == interface)
        {
            NS_LOG_INFO("Removed socket for interface " << interface);
            iter->first->Close();
            m_unicastSocketList.erase(iter);
            break;
        }
    }

    if (m_interfaceExclusions.find(interface) == m_interfaceExclusions.end())
    {
        SendTriggeredRouteUpdate();
    }
}

void
Rip::NotifyAddAddress(uint32_t interface, Ipv4InterfaceAddress address)
{
    NS_LOG_FUNCTION(this << interface << address);

    if (!m_ipv4->IsUp(interface))
    {
        return;
    }

    if (m_interfaceExclusions.find(interface) != m_interfaceExclusions.end())
    {
        return;
    }

    Ipv4Address networkAddress = address.GetLocal().CombineMask(address.GetMask());
    Ipv4Mask networkMask = address.GetMask();

    if (address.GetScope() == Ipv4InterfaceAddress::GLOBAL)
    {
        AddNetworkRouteTo(networkAddress, networkMask, interface);
    }

    SendTriggeredRouteUpdate();
}

void
Rip::NotifyRemoveAddress(uint32_t interface, Ipv4InterfaceAddress address)
{
    NS_LOG_FUNCTION(this << interface << address);

    if (!m_ipv4->IsUp(interface))
    {
        return;
    }

    if (address.GetScope() != Ipv4InterfaceAddress::GLOBAL)
    {
        return;
    }

    Ipv4Address networkAddress = address.GetLocal().CombineMask(address.GetMask());
    Ipv4Mask networkMask = address.GetMask();

    // Remove all routes that are going through this interface
    // which reference this network
    for (auto it = m_routes.begin(); it != m_routes.end(); it++)
    {
        if (it->first->GetInterface() == interface && it->first->IsNetwork() &&
            it->first->GetDestNetwork() == networkAddress &&
            it->first->GetDestNetworkMask() == networkMask)
        {
            InvalidateRoute(it->first);
        }
    }

    if (m_interfaceExclusions.find(interface) == m_interfaceExclusions.end())
    {
        SendTriggeredRouteUpdate();
    }
}

void
Rip::SetIpv4(Ptr<Ipv4> ipv4)
{
    NS_LOG_FUNCTION(this << ipv4);

    NS_ASSERT(!m_ipv4 && ipv4);
    uint32_t i = 0;
    m_ipv4 = ipv4;

    for (i = 0; i < m_ipv4->GetNInterfaces(); i++)
    {
        if (m_ipv4->IsUp(i))
        {
            NotifyInterfaceUp(i);
        }
        else
        {
            NotifyInterfaceDown(i);
        }
    }
}

void
Rip::PrintRoutingTable(Ptr<OutputStreamWrapper> stream, Time::Unit unit) const
{
    NS_LOG_FUNCTION(this << stream);

    std::ostream* os = stream->GetStream();
    // Copy the current ostream state
    std::ios oldState(nullptr);
    oldState.copyfmt(*os);

    *os << std::resetiosflags(std::ios::adjustfield) << std::setiosflags(std::ios::left);

    *os << "Node: " << m_ipv4->GetObject<Node>()->GetId() << ", Time: " << Now().As(unit)
        << ", Local time: " << m_ipv4->GetObject<Node>()->GetLocalTime().As(unit)
        << ", IPv4 RIP table" << std::endl;

    if (!m_routes.empty())
    {
        *os << "Destination     Gateway         Genmask         Flags Metric Ref    Use Iface"
            << std::endl;
        for (auto it = m_routes.begin(); it != m_routes.end(); it++)
        {
            RipRoutingTableEntry* route = it->first;
            RipRoutingTableEntry::Status_e status = route->GetRouteStatus();

            if (status == RipRoutingTableEntry::RIP_VALID)
            {
                std::ostringstream dest;
                std::ostringstream gw;
                std::ostringstream mask;
                std::ostringstream flags;
                dest << route->GetDest();
                *os << std::setw(16) << dest.str();
                gw << route->GetGateway();
                *os << std::setw(16) << gw.str();
                mask << route->GetDestNetworkMask();
                *os << std::setw(16) << mask.str();
                flags << "U";
                if (route->IsHost())
                {
                    flags << "HS";
                }
                else if (route->IsGateway())
                {
                    flags << "GS";
                }
                *os << std::setw(6) << flags.str();
                *os << std::setw(7) << int(route->GetRouteMetric());
                // Ref ct not implemented
                *os << "-"
                    << "      ";
                // Use not implemented
                *os << "-"
                    << "   ";
                if (!Names::FindName(m_ipv4->GetNetDevice(route->GetInterface())).empty())
                {
                    *os << Names::FindName(m_ipv4->GetNetDevice(route->GetInterface()));
                }
                else
                {
                    *os << route->GetInterface();
                }
                *os << std::endl;
            }
        }
    }
    *os << std::endl;
    // Restore the previous ostream state
    (*os).copyfmt(oldState);
}

void
Rip::DoDispose()
{
    NS_LOG_FUNCTION(this);

    for (auto j = m_routes.begin(); j != m_routes.end(); j = m_routes.erase(j))
    {
        delete j->first;
    }
    m_routes.clear();

    m_nextTriggeredUpdate.Cancel();
    m_nextUnsolicitedUpdate.Cancel();
    m_nextTriggeredUpdate = EventId();
    m_nextUnsolicitedUpdate = EventId();

    for (auto iter = m_unicastSocketList.begin(); iter != m_unicastSocketList.end(); iter++)
    {
        iter->first->Close();
    }
    m_unicastSocketList.clear();

    m_multicastRecvSocket->Close();
    m_multicastRecvSocket = nullptr;

    m_ipv4 = nullptr;

    Ipv4RoutingProtocol::DoDispose();
}

Ptr<Ipv4Route>
Rip::Lookup(Ipv4Address dst, bool setSource, Ptr<NetDevice> interface)
{
    NS_LOG_FUNCTION(this << dst << interface);

    Ptr<Ipv4Route> rtentry = nullptr;
    uint16_t longestMask = 0;

    /* when sending on local multicast, there have to be interface specified */
    if (dst.IsLocalMulticast())
    {
        NS_ASSERT_MSG(interface,
                      "Try to send on local multicast address, and no interface index is given!");
        rtentry = Create<Ipv4Route>();
        rtentry->SetSource(
            m_ipv4->SourceAddressSelection(m_ipv4->GetInterfaceForDevice(interface), dst));
        rtentry->SetDestination(dst);
        rtentry->SetGateway(Ipv4Address::GetZero());
        rtentry->SetOutputDevice(interface);
        return rtentry;
    }

    for (auto it = m_routes.begin(); it != m_routes.end(); it++)
    {
        RipRoutingTableEntry* j = it->first;

        if (j->GetRouteStatus() == RipRoutingTableEntry::RIP_VALID)
        {
            Ipv4Mask mask = j->GetDestNetworkMask();
            uint16_t maskLen = mask.GetPrefixLength();
            Ipv4Address entry = j->GetDestNetwork();

            NS_LOG_LOGIC("Searching for route to " << dst << ", mask length " << maskLen);

            if (mask.IsMatch(dst, entry))
            {
                NS_LOG_LOGIC("Found global network route " << j << ", mask length " << maskLen);

                /* if interface is given, check the route will output on this interface */
                if (!interface || interface == m_ipv4->GetNetDevice(j->GetInterface()))
                {
                    if (maskLen < longestMask)
                    {
                        NS_LOG_LOGIC("Previous match longer, skipping");
                        continue;
                    }

                    longestMask = maskLen;

                    Ipv4RoutingTableEntry* route = j;
                    uint32_t interfaceIdx = route->GetInterface();
                    rtentry = Create<Ipv4Route>();

                    if (setSource)
                    {
                        if (route->GetDest().IsAny()) /* default route */
                        {
                            rtentry->SetSource(
                                m_ipv4->SourceAddressSelection(interfaceIdx, route->GetGateway()));
                        }
                        else
                        {
                            rtentry->SetSource(
                                m_ipv4->SourceAddressSelection(interfaceIdx, route->GetDest()));
                        }
                    }

                    rtentry->SetDestination(route->GetDest());
                    rtentry->SetGateway(route->GetGateway());
                    rtentry->SetOutputDevice(m_ipv4->GetNetDevice(interfaceIdx));
                }
            }
        }
    }

    if (rtentry)
    {
        NS_LOG_LOGIC("Matching route via " << rtentry->GetDestination() << " (through "
                                           << rtentry->GetGateway() << ") at the end");
    }
    return rtentry;
}

void
Rip::AddNetworkRouteTo(Ipv4Address network,
                       Ipv4Mask networkPrefix,
                       Ipv4Address nextHop,
                       uint32_t interface)
{
    NS_LOG_FUNCTION(this << network << networkPrefix << nextHop << interface);

    auto route = new RipRoutingTableEntry(network, networkPrefix, nextHop, interface);
    route->SetRouteMetric(1);
    route->SetRouteStatus(RipRoutingTableEntry::RIP_VALID);
    route->SetRouteChanged(true);

    m_routes.emplace_back(route, EventId());
}

void
Rip::AddNetworkRouteTo(Ipv4Address network, Ipv4Mask networkPrefix, uint32_t interface)
{
    NS_LOG_FUNCTION(this << network << networkPrefix << interface);

    auto route = new RipRoutingTableEntry(network, networkPrefix, interface);
    route->SetRouteMetric(1);
    route->SetRouteStatus(RipRoutingTableEntry::RIP_VALID);
    route->SetRouteChanged(true);

    m_routes.emplace_back(route, EventId());
}

void
Rip::InvalidateRoute(RipRoutingTableEntry* route)
{
    NS_LOG_FUNCTION(this << *route);

    for (auto it = m_routes.begin(); it != m_routes.end(); it++)
    {
        if (it->first == route)
        {
            route->SetRouteStatus(RipRoutingTableEntry::RIP_INVALID);
            route->SetRouteMetric(m_linkDown);
            route->SetRouteChanged(true);
            if (it->second.IsRunning())
            {
                it->second.Cancel();
            }
            it->second =
                Simulator::Schedule(m_garbageCollectionDelay, &Rip::DeleteRoute, this, route);
            return;
        }
    }
    NS_ABORT_MSG("RIP::InvalidateRoute - cannot find the route to update");
}

void
Rip::DeleteRoute(RipRoutingTableEntry* route)
{
    NS_LOG_FUNCTION(this << *route);

    for (auto it = m_routes.begin(); it != m_routes.end(); it++)
    {
        if (it->first == route)
        {
            delete route;
            m_routes.erase(it);
            return;
        }
    }
    NS_ABORT_MSG("RIP::DeleteRoute - cannot find the route to delete");
}

void
Rip::Receive(Ptr<Socket> socket)
{
    NS_LOG_FUNCTION(this << socket);

    Address sender;
    Ptr<Packet> packet = socket->RecvFrom(sender);
    InetSocketAddress senderAddr = InetSocketAddress::ConvertFrom(sender);
    NS_LOG_INFO("Received " << *packet << " from " << senderAddr.GetIpv4() << ":"
                            << senderAddr.GetPort());

    Ipv4Address senderAddress = senderAddr.GetIpv4();
    uint16_t senderPort = senderAddr.GetPort();

    if (socket == m_multicastRecvSocket)
    {
        NS_LOG_LOGIC("Received a packet from the multicast socket");
    }
    else
    {
        NS_LOG_LOGIC("Received a packet from one of the unicast sockets");
    }

    Ipv4PacketInfoTag interfaceInfo;
    if (!packet->RemovePacketTag(interfaceInfo))
    {
        NS_ABORT_MSG("No incoming interface on RIP message, aborting.");
    }
    uint32_t incomingIf = interfaceInfo.GetRecvIf();
    Ptr<Node> node = this->GetObject<Node>();
    Ptr<NetDevice> dev = node->GetDevice(incomingIf);
    uint32_t ipInterfaceIndex = m_ipv4->GetInterfaceForDevice(dev);

    SocketIpTtlTag hoplimitTag;
    if (!packet->RemovePacketTag(hoplimitTag))
    {
        NS_ABORT_MSG("No incoming Hop Count on RIP message, aborting.");
    }
    uint8_t hopLimit = hoplimitTag.GetTtl();

    int32_t interfaceForAddress = m_ipv4->GetInterfaceForAddress(senderAddress);
    if (interfaceForAddress != -1)
    {
        NS_LOG_LOGIC("Ignoring a packet sent by myself.");
        return;
    }

    RipHeader hdr;
    packet->RemoveHeader(hdr);

    if (hdr.GetCommand() == RipHeader::RESPONSE)
    {
        NS_LOG_LOGIC("The message is a Response from " << senderAddr.GetIpv4() << ":"
                                                       << senderAddr.GetPort());
        HandleResponses(hdr, senderAddress, ipInterfaceIndex, hopLimit);
    }
    else if (hdr.GetCommand() == RipHeader::REQUEST)
    {
        NS_LOG_LOGIC("The message is a Request from " << senderAddr.GetIpv4() << ":"
                                                      << senderAddr.GetPort());
        HandleRequests(hdr, senderAddress, senderPort, ipInterfaceIndex, hopLimit);
    }
    else
    {
        NS_LOG_LOGIC("Ignoring message with unknown command: " << int(hdr.GetCommand()));
    }
}

void
Rip::HandleRequests(RipHeader requestHdr,
                    Ipv4Address senderAddress,
                    uint16_t senderPort,
                    uint32_t incomingInterface,
                    uint8_t hopLimit)
{
    NS_LOG_FUNCTION(this << senderAddress << int(senderPort) << incomingInterface << int(hopLimit)
                         << requestHdr);

    std::list<RipRte> rtes = requestHdr.GetRteList();

    if (rtes.empty())
    {
        return;
    }

    // check if it's a request for the full table from a neighbor
    if (rtes.size() == 1)
    {
        if (rtes.begin()->GetPrefix() == Ipv4Address::GetAny() &&
            rtes.begin()->GetSubnetMask().GetPrefixLength() == 0 &&
            rtes.begin()->GetRouteMetric() == m_linkDown)
        {
            // Output whole thing. Use Split Horizon
            if (m_interfaceExclusions.find(incomingInterface) == m_interfaceExclusions.end())
            {
                // we use one of the sending sockets, as they're bound to the right interface
                // and the local address might be used on different interfaces.
                Ptr<Socket> sendingSocket;
                for (auto iter = m_unicastSocketList.begin(); iter != m_unicastSocketList.end();
                     iter++)
                {
                    if (iter->second == incomingInterface)
                    {
                        sendingSocket = iter->first;
                    }
                }
                NS_ASSERT_MSG(sendingSocket,
                              "HandleRequest - Impossible to find a socket to send the reply");

                uint16_t mtu = m_ipv4->GetMtu(incomingInterface);
                uint16_t maxRte =
                    (mtu - Ipv4Header().GetSerializedSize() - UdpHeader().GetSerializedSize() -
                     RipHeader().GetSerializedSize()) /
                    RipRte().GetSerializedSize();

                Ptr<Packet> p = Create<Packet>();
                SocketIpTtlTag tag;
                p->RemovePacketTag(tag);
                if (senderAddress == Ipv4Address(RIP_ALL_NODE))
                {
                    tag.SetTtl(1);
                }
                else
                {
                    tag.SetTtl(255);
                }
                p->AddPacketTag(tag);

                RipHeader hdr;
                hdr.SetCommand(RipHeader::RESPONSE);

                for (auto rtIter = m_routes.begin(); rtIter != m_routes.end(); rtIter++)
                {
                    bool splitHorizoning = (rtIter->first->GetInterface() == incomingInterface);

                    Ipv4InterfaceAddress rtDestAddr =
                        Ipv4InterfaceAddress(rtIter->first->GetDestNetwork(),
                                             rtIter->first->GetDestNetworkMask());

                    bool isGlobal = (rtDestAddr.GetScope() == Ipv4InterfaceAddress::GLOBAL);
                    bool isDefaultRoute =
                        ((rtIter->first->GetDestNetwork() == Ipv4Address::GetAny()) &&
                         (rtIter->first->GetDestNetworkMask() == Ipv4Mask::GetZero()) &&
                         (rtIter->first->GetInterface() != incomingInterface));

                    if ((isGlobal || isDefaultRoute) &&
                        (rtIter->first->GetRouteStatus() == RipRoutingTableEntry::RIP_VALID))
                    {
                        RipRte rte;
                        rte.SetPrefix(rtIter->first->GetDestNetwork());
                        rte.SetSubnetMask(rtIter->first->GetDestNetworkMask());
                        if (m_splitHorizonStrategy == POISON_REVERSE && splitHorizoning)
                        {
                            rte.SetRouteMetric(m_linkDown);
                        }
                        else
                        {
                            rte.SetRouteMetric(rtIter->first->GetRouteMetric());
                        }
                        rte.SetRouteTag(rtIter->first->GetRouteTag());
                        if ((m_splitHorizonStrategy != SPLIT_HORIZON) ||
                            (m_splitHorizonStrategy == SPLIT_HORIZON && !splitHorizoning))
                        {
                            hdr.AddRte(rte);
                        }
                    }
                    if (hdr.GetRteNumber() == maxRte)
                    {
                        p->AddHeader(hdr);
                        NS_LOG_DEBUG("SendTo: " << *p);
                        sendingSocket->SendTo(p, 0, InetSocketAddress(senderAddress, RIP_PORT));
                        p->RemoveHeader(hdr);
                        hdr.ClearRtes();
                    }
                }
                if (hdr.GetRteNumber() > 0)
                {
                    p->AddHeader(hdr);
                    NS_LOG_DEBUG("SendTo: " << *p);
                    sendingSocket->SendTo(p, 0, InetSocketAddress(senderAddress, RIP_PORT));
                }
            }
        }
    }
    else
    {
        // note: we got the request as a single packet, so no check is necessary for MTU limit

        Ptr<Packet> p = Create<Packet>();
        SocketIpTtlTag tag;
        p->RemovePacketTag(tag);
        if (senderAddress == Ipv4Address(RIP_ALL_NODE))
        {
            tag.SetTtl(1);
        }
        else
        {
            tag.SetTtl(255);
        }
        p->AddPacketTag(tag);

        RipHeader hdr;
        hdr.SetCommand(RipHeader::RESPONSE);

        for (auto iter = rtes.begin(); iter != rtes.end(); iter++)
        {
            bool found = false;
            for (auto rtIter = m_routes.begin(); rtIter != m_routes.end(); rtIter++)
            {
                Ipv4InterfaceAddress rtDestAddr =
                    Ipv4InterfaceAddress(rtIter->first->GetDestNetwork(),
                                         rtIter->first->GetDestNetworkMask());
                if ((rtDestAddr.GetScope() == Ipv4InterfaceAddress::GLOBAL) &&
                    (rtIter->first->GetRouteStatus() == RipRoutingTableEntry::RIP_VALID))
                {
                    Ipv4Address requestedAddress = iter->GetPrefix();
                    requestedAddress.CombineMask(iter->GetSubnetMask());
                    Ipv4Address rtAddress = rtIter->first->GetDestNetwork();
                    rtAddress.CombineMask(rtIter->first->GetDestNetworkMask());

                    if (requestedAddress == rtAddress)
                    {
                        iter->SetRouteMetric(rtIter->first->GetRouteMetric());
                        iter->SetRouteTag(rtIter->first->GetRouteTag());
                        hdr.AddRte(*iter);
                        found = true;
                        break;
                    }
                }
            }
            if (!found)
            {
                iter->SetRouteMetric(m_linkDown);
                iter->SetRouteTag(0);
                hdr.AddRte(*iter);
            }
        }
        p->AddHeader(hdr);
        NS_LOG_DEBUG("SendTo: " << *p);
        m_multicastRecvSocket->SendTo(p, 0, InetSocketAddress(senderAddress, senderPort));
    }
}

void
Rip::HandleResponses(RipHeader hdr,
                     Ipv4Address senderAddress,
                     uint32_t incomingInterface,
                     uint8_t hopLimit)
{
    NS_LOG_FUNCTION(this << senderAddress << incomingInterface << int(hopLimit) << hdr);

    if (m_interfaceExclusions.find(incomingInterface) != m_interfaceExclusions.end())
    {
        NS_LOG_LOGIC(
            "Ignoring an update message from an excluded interface: " << incomingInterface);
        return;
    }

    std::list<RipRte> rtes = hdr.GetRteList();

    // validate the RTEs before processing
    for (auto iter = rtes.begin(); iter != rtes.end(); iter++)
    {
        if (iter->GetRouteMetric() == 0 || iter->GetRouteMetric() > m_linkDown)
        {
            NS_LOG_LOGIC("Ignoring an update message with malformed metric: "
                         << int(iter->GetRouteMetric()));
            return;
        }
        if (iter->GetPrefix().IsLocalhost() || iter->GetPrefix().IsBroadcast() ||
            iter->GetPrefix().IsMulticast())
        {
            NS_LOG_LOGIC("Ignoring an update message with wrong prefixes: " << iter->GetPrefix());
            return;
        }
    }

    bool changed = false;

    for (auto iter = rtes.begin(); iter != rtes.end(); iter++)
    {
        Ipv4Mask rtePrefixMask = iter->GetSubnetMask();
        Ipv4Address rteAddr = iter->GetPrefix().CombineMask(rtePrefixMask);

        NS_LOG_LOGIC("Processing RTE " << *iter);

        uint32_t interfaceMetric = 1;
        if (m_interfaceMetrics.find(incomingInterface) != m_interfaceMetrics.end())
        {
            interfaceMetric = m_interfaceMetrics[incomingInterface];
        }
        uint64_t rteMetric = iter->GetRouteMetric() + interfaceMetric;
        if (rteMetric > m_linkDown)
        {
            rteMetric = m_linkDown;
        }

        RoutesI it;
        bool found = false;
        for (it = m_routes.begin(); it != m_routes.end(); it++)
        {
            if (it->first->GetDestNetwork() == rteAddr &&
                it->first->GetDestNetworkMask() == rtePrefixMask)
            {
                found = true;
                if (rteMetric < it->first->GetRouteMetric())
                {
                    if (senderAddress != it->first->GetGateway())
                    {
                        auto route = new RipRoutingTableEntry(rteAddr,
                                                              rtePrefixMask,
                                                              senderAddress,
                                                              incomingInterface);
                        delete it->first;
                        it->first = route;
                    }
                    it->first->SetRouteMetric(rteMetric);
                    it->first->SetRouteStatus(RipRoutingTableEntry::RIP_VALID);
                    it->first->SetRouteTag(iter->GetRouteTag());
                    it->first->SetRouteChanged(true);
                    it->second.Cancel();
                    it->second =
                        Simulator::Schedule(m_timeoutDelay, &Rip::InvalidateRoute, this, it->first);
                    changed = true;
                }
                else if (rteMetric == it->first->GetRouteMetric())
                {
                    if (senderAddress == it->first->GetGateway())
                    {
                        it->second.Cancel();
                        it->second = Simulator::Schedule(m_timeoutDelay,
                                                         &Rip::InvalidateRoute,
                                                         this,
                                                         it->first);
                    }
                    else
                    {
                        if (Simulator::GetDelayLeft(it->second) < m_timeoutDelay / 2)
                        {
                            auto route = new RipRoutingTableEntry(rteAddr,
                                                                  rtePrefixMask,
                                                                  senderAddress,
                                                                  incomingInterface);
                            route->SetRouteMetric(rteMetric);
                            route->SetRouteStatus(RipRoutingTableEntry::RIP_VALID);
                            route->SetRouteTag(iter->GetRouteTag());
                            route->SetRouteChanged(true);
                            delete it->first;
                            it->first = route;
                            it->second.Cancel();
                            it->second = Simulator::Schedule(m_timeoutDelay,
                                                             &Rip::InvalidateRoute,
                                                             this,
                                                             route);
                            changed = true;
                        }
                    }
                }
                else if (rteMetric > it->first->GetRouteMetric() &&
                         senderAddress == it->first->GetGateway())
                {
                    it->second.Cancel();
                    if (rteMetric < m_linkDown)
                    {
                        it->first->SetRouteMetric(rteMetric);
                        it->first->SetRouteStatus(RipRoutingTableEntry::RIP_VALID);
                        it->first->SetRouteTag(iter->GetRouteTag());
                        it->first->SetRouteChanged(true);
                        it->second.Cancel();
                        it->second = Simulator::Schedule(m_timeoutDelay,
                                                         &Rip::InvalidateRoute,
                                                         this,
                                                         it->first);
                    }
                    else
                    {
                        InvalidateRoute(it->first);
                    }
                    changed = true;
                }
            }
        }
        if (!found && rteMetric != m_linkDown)
        {
            NS_LOG_LOGIC("Received a RTE with new route, adding.");

            auto route =
                new RipRoutingTableEntry(rteAddr, rtePrefixMask, senderAddress, incomingInterface);
            route->SetRouteMetric(rteMetric);
            route->SetRouteStatus(RipRoutingTableEntry::RIP_VALID);
            route->SetRouteChanged(true);
            m_routes.emplace_front(route, EventId());
            EventId invalidateEvent =
                Simulator::Schedule(m_timeoutDelay, &Rip::InvalidateRoute, this, route);
            (m_routes.begin())->second = invalidateEvent;
            changed = true;
        }
    }

    if (changed)
    {
        SendTriggeredRouteUpdate();
    }
}

void
Rip::DoSendRouteUpdate(bool periodic)
{
    NS_LOG_FUNCTION(this << (periodic ? " periodic" : " triggered"));

    for (auto iter = m_unicastSocketList.begin(); iter != m_unicastSocketList.end(); iter++)
    {
        uint32_t interface = iter->second;

        if (m_interfaceExclusions.find(interface) == m_interfaceExclusions.end())
        {
            uint16_t mtu = m_ipv4->GetMtu(interface);
            uint16_t maxRte = (mtu - Ipv4Header().GetSerializedSize() -
                               UdpHeader().GetSerializedSize() - RipHeader().GetSerializedSize()) /
                              RipRte().GetSerializedSize();

            Ptr<Packet> p = Create<Packet>();
            SocketIpTtlTag tag;
            tag.SetTtl(1);
            p->AddPacketTag(tag);

            RipHeader hdr;
            hdr.SetCommand(RipHeader::RESPONSE);

            for (auto rtIter = m_routes.begin(); rtIter != m_routes.end(); rtIter++)
            {
                bool splitHorizoning = (rtIter->first->GetInterface() == interface);
                Ipv4InterfaceAddress rtDestAddr =
                    Ipv4InterfaceAddress(rtIter->first->GetDestNetwork(),
                                         rtIter->first->GetDestNetworkMask());

                NS_LOG_DEBUG("Processing RT " << rtDestAddr << " "
                                              << int(rtIter->first->IsRouteChanged()));

                bool isGlobal = (rtDestAddr.GetScope() == Ipv4InterfaceAddress::GLOBAL);
                bool isDefaultRoute =
                    ((rtIter->first->GetDestNetwork() == Ipv4Address::GetAny()) &&
                     (rtIter->first->GetDestNetworkMask() == Ipv4Mask::GetZero()) &&
                     (rtIter->first->GetInterface() != interface));

                bool sameNetwork = false;
                for (uint32_t index = 0; index < m_ipv4->GetNAddresses(interface); index++)
                {
                    Ipv4InterfaceAddress addr = m_ipv4->GetAddress(interface, index);
                    if (addr.GetLocal().CombineMask(addr.GetMask()) ==
                        rtIter->first->GetDestNetwork())
                    {
                        sameNetwork = true;
                    }
                }

                if ((isGlobal || isDefaultRoute) && (periodic || rtIter->first->IsRouteChanged()) &&
                    !sameNetwork)
                {
                    RipRte rte;
                    rte.SetPrefix(rtIter->first->GetDestNetwork());
                    rte.SetSubnetMask(rtIter->first->GetDestNetworkMask());
                    if (m_splitHorizonStrategy == POISON_REVERSE && splitHorizoning)
                    {
                        rte.SetRouteMetric(m_linkDown);
                    }
                    else
                    {
                        rte.SetRouteMetric(rtIter->first->GetRouteMetric());
                    }
                    rte.SetRouteTag(rtIter->first->GetRouteTag());
                    if (m_splitHorizonStrategy == SPLIT_HORIZON && !splitHorizoning)
                    {
                        hdr.AddRte(rte);
                    }
                    else if (m_splitHorizonStrategy != SPLIT_HORIZON)
                    {
                        hdr.AddRte(rte);
                    }
                }
                if (hdr.GetRteNumber() == maxRte)
                {
                    p->AddHeader(hdr);
                    NS_LOG_DEBUG("SendTo: " << *p);
                    iter->first->SendTo(p, 0, InetSocketAddress(RIP_ALL_NODE, RIP_PORT));
                    p->RemoveHeader(hdr);
                    hdr.ClearRtes();
                }
            }
            if (hdr.GetRteNumber() > 0)
            {
                p->AddHeader(hdr);
                NS_LOG_DEBUG("SendTo: " << *p);
                iter->first->SendTo(p, 0, InetSocketAddress(RIP_ALL_NODE, RIP_PORT));
            }
        }
    }
    for (auto rtIter = m_routes.begin(); rtIter != m_routes.end(); rtIter++)
    {
        rtIter->first->SetRouteChanged(false);
    }
}

void
Rip::SendTriggeredRouteUpdate()
{
    NS_LOG_FUNCTION(this);

    if (m_nextTriggeredUpdate.IsRunning())
    {
        NS_LOG_LOGIC("Skipping Triggered Update due to cooldown");
        return;
    }

    // DoSendRouteUpdate (false);

    // note: The RFC states:
    //     After a triggered
    //     update is sent, a timer should be set for a random interval between 1
    //     and 5 seconds.  If other changes that would trigger updates occur
    //     before the timer expires, a single update is triggered when the timer
    //     expires.  The timer is then reset to another random value between 1
    //     and 5 seconds.  Triggered updates may be suppressed if a regular
    //     update is due by the time the triggered update would be sent.
    // Here we rely on this:
    // When an update occurs (either Triggered or Periodic) the "IsChanged ()"
    // route field will be cleared.
    // Hence, the following Triggered Update will be fired, but will not send
    // any route update.

    Time delay = Seconds(m_rng->GetValue(m_minTriggeredUpdateDelay.GetSeconds(),
                                         m_maxTriggeredUpdateDelay.GetSeconds()));
    m_nextTriggeredUpdate = Simulator::Schedule(delay, &Rip::DoSendRouteUpdate, this, false);
}

void
Rip::SendUnsolicitedRouteUpdate()
{
    NS_LOG_FUNCTION(this);

    if (m_nextTriggeredUpdate.IsRunning())
    {
        m_nextTriggeredUpdate.Cancel();
    }

    DoSendRouteUpdate(true);

    Time delay =
        m_unsolicitedUpdate + Seconds(m_rng->GetValue(0, 0.5 * m_unsolicitedUpdate.GetSeconds()));
    m_nextUnsolicitedUpdate = Simulator::Schedule(delay, &Rip::SendUnsolicitedRouteUpdate, this);
}

std::set<uint32_t>
Rip::GetInterfaceExclusions() const
{
    return m_interfaceExclusions;
}

void
Rip::SetInterfaceExclusions(std::set<uint32_t> exceptions)
{
    NS_LOG_FUNCTION(this);

    m_interfaceExclusions = exceptions;
}

uint8_t
Rip::GetInterfaceMetric(uint32_t interface) const
{
    NS_LOG_FUNCTION(this << interface);

    auto iter = m_interfaceMetrics.find(interface);
    if (iter != m_interfaceMetrics.end())
    {
        return iter->second;
    }
    return 1;
}

void
Rip::SetInterfaceMetric(uint32_t interface, uint8_t metric)
{
    NS_LOG_FUNCTION(this << interface << int(metric));

    if (metric < m_linkDown)
    {
        m_interfaceMetrics[interface] = metric;
    }
}

void
Rip::SendRouteRequest()
{
    NS_LOG_FUNCTION(this);

    Ptr<Packet> p = Create<Packet>();
    SocketIpTtlTag tag;
    p->RemovePacketTag(tag);
    tag.SetTtl(1);
    p->AddPacketTag(tag);

    RipHeader hdr;
    hdr.SetCommand(RipHeader::REQUEST);

    RipRte rte;
    rte.SetPrefix(Ipv4Address::GetAny());
    rte.SetSubnetMask(Ipv4Mask::GetZero());
    rte.SetRouteMetric(m_linkDown);

    hdr.AddRte(rte);
    p->AddHeader(hdr);

    for (auto iter = m_unicastSocketList.begin(); iter != m_unicastSocketList.end(); iter++)
    {
        uint32_t interface = iter->second;

        if (m_interfaceExclusions.find(interface) == m_interfaceExclusions.end())
        {
            NS_LOG_DEBUG("SendTo: " << *p);
            iter->first->SendTo(p, 0, InetSocketAddress(RIP_ALL_NODE, RIP_PORT));
        }
    }
}

void
Rip::AddDefaultRouteTo(Ipv4Address nextHop, uint32_t interface)
{
    NS_LOG_FUNCTION(this << interface);

    AddNetworkRouteTo(Ipv4Address("0.0.0.0"), Ipv4Mask::GetZero(), nextHop, interface);
}

/*
 * RipRoutingTableEntry
 */

RipRoutingTableEntry::RipRoutingTableEntry()
    : m_tag(0),
      m_metric(0),
      m_status(RIP_INVALID),
      m_changed(false)
{
}

RipRoutingTableEntry::RipRoutingTableEntry(Ipv4Address network,
                                           Ipv4Mask networkPrefix,
                                           Ipv4Address nextHop,
                                           uint32_t interface)
    : Ipv4RoutingTableEntry(
          Ipv4RoutingTableEntry::CreateNetworkRouteTo(network, networkPrefix, nextHop, interface)),
      m_tag(0),
      m_metric(0),
      m_status(RIP_INVALID),
      m_changed(false)
{
}

RipRoutingTableEntry::RipRoutingTableEntry(Ipv4Address network,
                                           Ipv4Mask networkPrefix,
                                           uint32_t interface)
    : Ipv4RoutingTableEntry(
          Ipv4RoutingTableEntry::CreateNetworkRouteTo(network, networkPrefix, interface)),
      m_tag(0),
      m_metric(0),
      m_status(RIP_INVALID),
      m_changed(false)
{
}

RipRoutingTableEntry::~RipRoutingTableEntry()
{
}

void
RipRoutingTableEntry::SetRouteTag(uint16_t routeTag)
{
    if (m_tag != routeTag)
    {
        m_tag = routeTag;
        m_changed = true;
    }
}

uint16_t
RipRoutingTableEntry::GetRouteTag() const
{
    return m_tag;
}

void
RipRoutingTableEntry::SetRouteMetric(uint8_t routeMetric)
{
    if (m_metric != routeMetric)
    {
        m_metric = routeMetric;
        m_changed = true;
    }
}

uint8_t
RipRoutingTableEntry::GetRouteMetric() const
{
    return m_metric;
}

void
RipRoutingTableEntry::SetRouteStatus(Status_e status)
{
    if (m_status != status)
    {
        m_status = status;
        m_changed = true;
    }
}

RipRoutingTableEntry::Status_e
RipRoutingTableEntry::GetRouteStatus() const
{
    return m_status;
}

void
RipRoutingTableEntry::SetRouteChanged(bool changed)
{
    m_changed = changed;
}

bool
RipRoutingTableEntry::IsRouteChanged() const
{
    return m_changed;
}

std::ostream&
operator<<(std::ostream& os, const RipRoutingTableEntry& rte)
{
    os << static_cast<const Ipv4RoutingTableEntry&>(rte);
    os << ", metric: " << int(rte.GetRouteMetric()) << ", tag: " << int(rte.GetRouteTag());

    return os;
}

} // namespace ns3
